package com.ramnight.unify.kit;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.BeanMap;
import org.apache.commons.collections4.MapUtils;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

@SuppressWarnings({ "rawtypes" })
public class UnifyMap implements Map<String, Object> {
	private HashMap<String, Object> holder;

	private boolean isCreateFromJson = false;
	private boolean isInitJsonAnalyzed = false;
	// 每次onModify都清除，不put则不清除
	private String json = null;
	private boolean isJsonSortKey = false;

	// 反射用
	public UnifyMap() {
		holder = Maps.newHashMap();
	}

	private UnifyMap(int initialCapacity) {
		holder = Maps.newHashMapWithExpectedSize(initialCapacity);
	}

	private UnifyMap(Map<String, Object> map) {
		holder = Maps.newHashMap(map);
	}

	public static UnifyMap of() {
		return new UnifyMap();
	}

	public static UnifyMap ofMap(Map<?, ?> map) {
		UnifyMap instance = new UnifyMap();
		MapUtils.emptyIfNull(map).entrySet().stream().forEach(entry -> {
			instance.add(String.valueOf(entry.getKey()), entry.getValue());
		});
		return instance;
	}

	public static UnifyMap ofCapacity(int initialCapacity) {
		return new UnifyMap(initialCapacity);
	}

	public static UnifyMap ofJson(String jsonString) {
		UnifyMap instance = new UnifyMap(UnifyConverter.jsonParser.fromJsonToMap(jsonString));
		instance.json = jsonString;
		instance.isInitJsonAnalyzed = true;
		instance.isCreateFromJson = true;
		return instance;
	}

	public static UnifyMap ofJsonLazy(String jsonString) {
		UnifyMap instance = new UnifyMap();
		instance.json = jsonString;
		instance.isInitJsonAnalyzed = false;
		instance.isCreateFromJson = true;
		return instance;
	}

	public static UnifyMap ofBean(Object bean) {
		UnifyMap instance = new UnifyMap();
		if (bean != null) {
			BeanMap beanMap = new BeanMap(bean);
			for (Object key : beanMap.keySet()) {
				Object value = beanMap.get(key);
				instance.put(key + "", value);
			}
		}
		return instance;
	}

	public static UnifyMap wrap(String field, Object value) {
		UnifyMap instance = new UnifyMap(1);
		return instance.add(field, value);
	}

	public static UnifyMap empty() {
		return new UnifyMap(0);
	}

	public UnifyMap add(String field, Object value) {
		put(field, value);
		return this;
	}

	public UnifyMap addAsList(String field, Object... objects) {
		put(field, Lists.newArrayList(objects));
		return this;
	}

	public UnifyMap addAsSet(String field, Object... objects) {
		put(field, Sets.newHashSet(objects));
		return this;
	}

	public String toJson(boolean isSortKey) {
		if (isSortKey) {
			if (json != null && isJsonSortKey) {
				return json;
			} else {
				this.json = UnifyConverter.jsonParser.toJsonSorted(holder);
				this.isJsonSortKey = true;
			}
		} else {
			if (json != null) {
				return json;
			} else {
				this.json = UnifyConverter.jsonParser.toJson(holder);
				this.isJsonSortKey = false;
			}
		}

		return json;
	}

	@Override
	public String toString() {
		return this.toJson(false);
	}

	public <T> T to(Class<T> clazz) {
		return UnifyConverter.mapToBean(this, clazz);
	}

	private void onModify() {
		initLazyJson();
		this.json = null;
	}

	private void onRead() {
		initLazyJson();
	}

	private void initLazyJson() {
		if (isCreateFromJson && !isInitJsonAnalyzed && json != null) {
			holder.putAll(UnifyConverter.jsonParser.fromJsonToMap(json));
			isInitJsonAnalyzed = true;
		}
	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof UnifyMap) {
			return this.toJson(true).equals(((UnifyMap) obj).toJson(true));
		}
		return false;
	}

	@Override
	public int hashCode() {
		return this.toJson(true).hashCode();
	}

	// -------------------------------------------------------------------------------------

	public Long getLong(String field) {
		Object obj = this.get(field);
		return UnifyConverter.toLong(obj);
	}

	public Long getLong(String field, Long defaultValue) {
		return UnifyConverter.nullToDefault(getLong(field), defaultValue);
	}

	public Double getDouble(String field) {
		Object obj = this.get(field);
		return UnifyConverter.toDouble(obj);
	}

	public Double getDouble(String field, Double defaultValue) {
		return UnifyConverter.nullToDefault(getDouble(field), defaultValue);
	}

	public String getString(String field) {
		Object obj = this.get(field);
		return UnifyConverter.toString(obj);
	}

	public String getString(String field, String defaultValue) {
		return UnifyConverter.nullToDefault(getString(field), defaultValue);
	}

	public Integer getInteger(String field) {
		Object obj = this.get(field);
		return UnifyConverter.toInteger(obj);
	}

	public Integer getInteger(String field, Integer defaultValue) {
		return UnifyConverter.nullToDefault(getInteger(field), defaultValue);
	}

	public Boolean getBoolean(String field) {
		Object obj = this.get(field);
		return UnifyConverter.toBoolean(obj);
	}

	public Boolean getBoolean(String field, Boolean defaultValue) {
		return UnifyConverter.nullToDefault(getBoolean(field), defaultValue);
	}

	public UnifyMap getMap(String field) {
		Object obj = this.get(field);
		return UnifyConverter.toUnifyMap(obj);
	}

	public UnifyMap getMapNotNull(String field) {
		return UnifyConverter.nullToDefault(getMap(field), empty());
	}

	public Map getMapDirect(String field) {
		Object obj = this.get(field);
		return UnifyConverter.toMapDirect(obj);
	}

	public <T> T getBean(String field, Class<T> clazz) {
		Object obj = this.get(field);
		return UnifyConverter.toBean(obj, clazz);
	}

	public <T> T getBean(String field, Class<T> clazz, T defaultValue) {
		return UnifyConverter.nullToDefault(getBean(field, clazz), defaultValue);
	}

	public List getListDirect(String field) {
		Object obj = this.get(field);
		return UnifyConverter.toListDirect(obj);
	}

	public List<UnifyMap> getList(String field) {
		Object obj = this.get(field);
		return UnifyConverter.toList(obj, UnifyMap.class);
	}

	public <T> List<T> getList(String field, Class<T> clazz) {
		Object obj = this.get(field);
		return UnifyConverter.toList(obj, clazz);
	}

	public List<UnifyMap> getListNotNull(String field) {
		List<UnifyMap> result = getList(field);
		return result != null ? result : Collections.emptyList();
	}

	public <T> List<T> getListNotNull(String field, Class<T> clazz) {
		List<T> result = getList(field, clazz);
		return result != null ? result : Collections.emptyList();
	}

	public UnifyPage getPage(String field) {
		return getBean(field, UnifyPage.class);
	}

	// -------------------------------------------------------------------------

	@Override
	public int size() {
		onRead();
		return holder.size();
	}

	@Override
	public boolean isEmpty() {
		onRead();
		return holder.isEmpty();
	}

	@Override
	public boolean containsKey(Object key) {
		onRead();
		return holder.containsKey(key);
	}

	@Override
	public boolean containsValue(Object value) {
		onRead();
		return holder.containsValue(value);
	}

	@Override
	public Object get(Object key) {
		onRead();
		return holder.get(key);
	}

	@Override
	public Object put(String key, Object value) {
		onModify();
		return holder.put(key, value);
	}

	@Override
	public Object remove(Object key) {
		onModify();
		return holder.remove(key);
	}

	@Override
	public void putAll(Map<? extends String, ? extends Object> m) {
		onModify();
		holder.putAll(m);
	}

	@Override
	public void clear() {
		onModify();
		holder.clear();
	}

	@Override
	public Set<String> keySet() {
		onRead();
		return holder.keySet();
	}

	@Override
	public Collection<Object> values() {
		onRead();
		return holder.values();
	}

	@Override
	public Set<Entry<String, Object>> entrySet() {
		onRead();
		return holder.entrySet();
	}
}
